package com.alibaba.shopping_search_service.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.FieldSuggester;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
import co.elastic.clients.elasticsearch.indices.AnalyzeResponse;
import co.elastic.clients.elasticsearch.indices.analyze.AnalyzeToken;
import co.elastic.clients.json.JsonData;
import com.alibaba.shopping_common.pojo.*;
import com.alibaba.shopping_common.service.SearchService;
import com.alibaba.shopping_search_service.es.GoodsRepository;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.SneakyThrows;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * @ClassName SearchServiceImpl
 * @Description TODO
 * @Author Leo
 * @Date 2024/12/12 18:16
 */
@Service
@DubboService
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private ElasticsearchTemplate template;

    /**
     * 对文本进行分词
     * @param text 被分词的文本
     * @param analyzer 分词器类型
     * @return
     */
    @SneakyThrows
    @Override
    public List<String> analyze(String text, String analyzer) {
        // 1. 创建分词请求
        AnalyzeRequest request = AnalyzeRequest.of(a -> a.index("goods").analyzer(analyzer).text(text));
        // 2. 发送分词请求
        AnalyzeResponse response = elasticsearchClient.indices().analyze(request);
        // 3. 处理分词请求
        List<String> words = new ArrayList<>();
        List<AnalyzeToken> tokens = response.tokens();
        // 遍历结果集合
        for (AnalyzeToken token : tokens) {
            String word = token.token();
            // 塞入words中
            words.add(word);
        }
        return words;
    }

    /**
     * 自动补齐
     * @param keyword
     * @return
     */
    @SneakyThrows
    @Override
    public List<String> autoSuggest(String keyword) {
        // 1. 自动补齐的查询条件
        Suggester suggester = Suggester.of(
                s -> s.suggesters("prefix_suggestion", FieldSuggester.of(
                        fs -> fs.completion(
                                // 是否对查询结果进行去重
                                cs -> cs.skipDuplicates(true)
                                        // 对哪个域进行查询
                                        .field("tags")
                                        // 返回10条结果
                                        .size(10)
                        )
                )).text(keyword)
        );

        // 2. 进行自动补齐的结果查询
        SearchResponse<Map> response = elasticsearchClient.search(s -> s.index("goods")
                .suggest(suggester), Map.class);

        // 3.处理查询结果
        Map resultMap = response.suggest();
        List<Suggestion> suggestionList = (List) resultMap.get("prefix_suggestion");
        Suggestion suggestion = suggestionList.get(0);
        List<CompletionSuggestOption> resultList = suggestion.completion().options();

        List<String> result = new ArrayList<>();
        for (CompletionSuggestOption completionSuggestOption : resultList) {
            String text = completionSuggestOption.text();
            result.add(text);
        }
        return result;
    }


    @Override
    public GoodsSearchResult search(GoodsSearchParam goodsSearchParam) {
        // 1.构造ES搜索条件
        NativeQuery nativeQuery = buildQuery(goodsSearchParam);
        // 2.搜索
        SearchHits<GoodsES> search = template.search(nativeQuery, GoodsES.class);
        // 3.将查询结果封装为Mybatis-plus的Page对象
        // 3.1 将SearchHits对象转为List
        List<GoodsES> content = new ArrayList();
        for (SearchHit<GoodsES> goodsESSearchHit : search) {
            GoodsES goodsES = goodsESSearchHit.getContent();
            content.add(goodsES);
        }
        // 3.2 将List转为Mybatis-plus的Page对象
        Page<GoodsES> page = new Page();
        page.setCurrent(goodsSearchParam.getPage()) // 当前页
                .setSize(goodsSearchParam.getSize()) // 每页条数
                .setTotal(search.getTotalHits()) // 总条数
                .setRecords(content); // 结果集

        // 4.封装查询结果
        GoodsSearchResult result = new GoodsSearchResult();
        // 4.1 封装商品
        result.setGoodsPage(page);
        // 4.2 封装查询参数
        result.setGoodsSearchParam(goodsSearchParam);
        // 4.3 封装查询面板
        buildSearchPanel(goodsSearchParam,result);
        return result;
    }

    /**
     * 封装查询面板，即根据查询条件，找到查询结果关联度前20名的商品进行封装
     * @param goodsSearchParam 查询条件对象
     * @param goodsSearchResult 查询结果对象
     */
    public void buildSearchPanel(GoodsSearchParam goodsSearchParam,GoodsSearchResult goodsSearchResult){
        // 1.构造查询条件
        goodsSearchParam.setPage(1);
        goodsSearchParam.setSize(20);
        goodsSearchParam.setSort(null);
        goodsSearchParam.setSortFiled(null);
        NativeQuery nativeQuery = buildQuery(goodsSearchParam);
        // 2.搜索
        SearchHits<GoodsES> search = template.search(nativeQuery, GoodsES.class);
        // 3.将结果封装为List对象
        List<GoodsES> content = new ArrayList();
        for (SearchHit<GoodsES> goodsESSearchHit : search) {
            GoodsES goodsES = goodsESSearchHit.getContent();
            content.add(goodsES);
        }

        // 4.遍历集合，封装查询面板
        // 商品相关的品牌列表
        Set<String> brands = new HashSet();
        // 商品相关的类型列表
        Set<String> productTypes = new HashSet();
        // 商品相关的规格列表
        Map<String,Set<String>> specifications = new HashMap();

        for (GoodsES goodsES : content) {
            // 获取品牌
            brands.add(goodsES.getBrand());
            // 获取类型
            List<String> productType = goodsES.getProductType();
            productTypes.addAll(productType);
            // 获取规格
            Map<String, List<String>> specification = goodsES.getSpecification();
            Set<Map.Entry<String, List<String>>> entries = specification.entrySet();
            for (Map.Entry<String, List<String>> entry : entries) {
                // 规格名
                String key = entry.getKey();
                // 规格值
                List<String> value = entry.getValue();
                // 如果specifications有该规格，则像规格中添加规格项，如果没有该规格，新增键值对
                if (!specifications.containsKey(key)){
                    specifications.put(key,new HashSet(value));
                }else {
                    specifications.get(key).addAll(value);
                }
            }
        }
        goodsSearchResult.setBrands(brands);
        goodsSearchResult.setProductType(productTypes);
        goodsSearchResult.setSpecifications(specifications);
    }

    /**
     * 构造查询条件
     * @param goodsSearchParam
     * @return
     */
    public NativeQuery buildQuery(GoodsSearchParam goodsSearchParam){
        // 1. 创建复杂的查询对象
        NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
        BoolQuery.Builder builder = new BoolQuery.Builder(); // 用于连接多个查询条件

        // 2.如果用户输入为空，则查询所有商品
        if (!StringUtils.hasText(goodsSearchParam.getKeyword())){
            // 如果用户输入为空，则查询所有商品
            MatchAllQuery matchAllQuery = new MatchAllQuery.Builder().build();
            builder.must(matchAllQuery._toQuery());
        }else {
            // 如果用户输入了查询关键词，例如：苹果，可以匹配商品的商品名称、副标题、品牌字段
            String keyword = goodsSearchParam.getKeyword();
            // 使用keyword对多个域的字段进行精准匹配，每个域之间是 或 的关系
            MultiMatchQuery multiMatchQuery = MultiMatchQuery.of(q -> q.query(keyword)
                    .fields("goodsName","caption","brand"));
            builder.must(multiMatchQuery._toQuery());
        }

        // 3. 如果查询条件有品牌信息
        String brand = goodsSearchParam.getBrand();
        if (StringUtils.hasText(brand)){
            //品牌字段不需要再分词，直接精准匹配
            TermQuery termQuery = TermQuery.of(q -> q.field("brand").value(brand));
            builder.must(termQuery._toQuery());
        }

        // 4. 如果输入了价格区间，则匹配价格
        Double highPrice = goodsSearchParam.getHighPrice();
        Double lowPrice = goodsSearchParam.getLowPrice();
        if (highPrice != null && highPrice != 0){
            RangeQuery lte = RangeQuery.of(q -> q.field("price").lte(JsonData.of(highPrice)));
            builder.must(lte._toQuery());
        }
        if (lowPrice != null && lowPrice != 0){
            RangeQuery gte = RangeQuery.of(q -> q.field("price").gte(JsonData.of(lowPrice)));
            builder.must(gte._toQuery());
        }

        // 5.如果查询条件有规格项，则进准匹配规格项
        Map<String, String> specificationOption = goodsSearchParam.getSpecificationOption();
        if (specificationOption !=null && !specificationOption.isEmpty()){
            Set<Map.Entry<String, String>> entries = specificationOption.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (StringUtils.hasText(key)){
                    TermQuery termQuery = TermQuery.of(q -> q.field("specification."
                            +key+".keyword").value(value));
                    builder.must(termQuery._toQuery());
                }
            }
        }
        nativeQueryBuilder.withQuery(builder.build()._toQuery());

        // 6. 添加分页查询的条件
        PageRequest pageRequest = PageRequest.of(
                goodsSearchParam.getPage() -1,goodsSearchParam.getSize());
        nativeQueryBuilder.withPageable(pageRequest);

        // 7. 添加排序条件
        String sortFiled = goodsSearchParam.getSortFiled();
        String sort = goodsSearchParam.getSort();
        if (StringUtils.hasText(sort) && StringUtils.hasText(sortFiled)){
            Sort sortParam = null;
            // 新品的正序是ID的倒序
            if (sortFiled.equals("NEW")){
                if (sort.equals("ASC")){
                    sortParam = Sort.by(Sort.Direction.DESC,"id");
                }
                if (sort.equals("DESC")){
                    sortParam = Sort.by(Sort.Direction.ASC,"id");
                }
            }
            if (sortFiled.equals("PRICE")){
                if (sort.equals("ASC")){
                    sortParam = Sort.by(Sort.Direction.ASC,"price");
                }
                if (sort.equals("DESC")){
                    sortParam = Sort.by(Sort.Direction.DESC,"price");
                }
            }
            nativeQueryBuilder.withSort(sortParam);
        }
        // 8.返回封装好的搜索条件对象
        return nativeQueryBuilder.build();
    }

    /**
     * 向ES同步商品数据
     * @param goodsDesc
     */
    @Override
    public void syncGoodsToES(GoodsDesc goodsDesc) {
        // 将商品详情对象转化成GoodsES对象
        GoodsES goodsES = new GoodsES();
        // 商品ID
        goodsES.setId(goodsDesc.getId());
        // 商品名称
        goodsES.setGoodsName(goodsDesc.getGoodsName());
        // 商品价格
        goodsES.setPrice(goodsDesc.getPrice());
        // 商品副标题
        goodsES.setCaption(goodsDesc.getCaption());
        // 商品的品牌名称(ES中只存储品牌名称)
        goodsES.setBrand(goodsDesc.getBrand().getName());
        // 头图
        goodsES.setHeaderPic(goodsDesc.getHeaderPic());
        // 将商品的一、二、三类型写入一个集合中
        List<String> productType = new ArrayList<>();
        productType.add(goodsDesc.getProductType1().getName());
        productType.add(goodsDesc.getProductType2().getName());
        productType.add(goodsDesc.getProductType3().getName());
        // 商品类目
        goodsES.setProductType(productType);
        // 规格集合
        Map<String,List<String>>map=new HashMap<>();
        List<Specification> specifications = new ArrayList<>();
        // 遍历规格
        for (Specification specification : specifications) {
            // 获取规格项集合
            List<SpecificationOption> options = specification.getSpecificationOptions();
            // 规格项经常集合
            List<String> optionStrList = new ArrayList<>();
            for (SpecificationOption option : options) {
                optionStrList.add(option.getOptionName());
            }
            map.put(specification.getSpecName(),optionStrList);
        }
        goodsES.setSpecification(map);

        // 自动补齐的关键字
        List<String>tags = new ArrayList<>();
        // 将品牌名称作为搜索的关键字
        tags.add(goodsDesc.getBrand().getName());
        // 将商品名称分词后作为搜索的关键字，分词器类型为中文_拼音分词器
        tags.addAll(analyze(goodsDesc.getGoodsName(),"ik_pinyin"));
        // 将商品副标题分词后作为搜索的关键字，分词器类型为中文_拼音分词器
        tags.addAll(analyze(goodsDesc.getCaption(),"ik_pinyin"));
        goodsES.setTags(tags);
        // 将goodsES对象写入ES
        goodsRepository.save(goodsES);
    }

    /**
     * 管理员下架商品时删除ES中的数据
     * @param id 商品ID
     */
    @Override
    public void delete(Long id) {
        goodsRepository.deleteById(id);
    }
}




